package concordia.comp6231.server;

import static concordia.comp6231.common.ApplicationConfig.REPLICA_BOX_OFFICE_ID;
import static concordia.comp6231.common.ApplicationConfig.REPLICA_GROUP_NAME;
import static concordia.comp6231.common.ApplicationConfig.REPLICA_PROCESS_ID;
import static concordia.comp6231.common.ApplicationConfig.REPLICA_SHOWS_BASE_DIRECTORY;
import static concordia.comp6231.common.ApplicationConfig.REPLICA_STATE;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

import org.omg.CORBA.ORB;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.PortableServer.POA;

import concordia.comp6231.boxOfficeService.MyService;
import concordia.comp6231.boxOfficeService.MyServiceHelper;
import concordia.comp6231.common.ApplicationConfig;
import concordia.comp6231.failuredetection.FailureDetector;
import concordia.comp6231.groupmembership.GroupMembershipManager;
import concordia.comp6231.leaderelection.LeaderElectionManager;

public class ReplicaMember {
	
	public static String LEADER_REPLICA_SERVICE_NAME = "LEADER_REPLICA_SERVICE"; 
	public static String REPLICA_STATE_LEADER = "leader";
	public static String REPLICA_STATE_MEMBER = "member";

	private int processId;
	private String state;
	private String boxOfficeId;
	private String groupName;
	
	private LeaderElectionManager leaderElectionManager;
	private GroupMembershipManager grupMembershipManager;
	private FailureDetector failureDetector;
	private BoxOfficeImpl boxOfficeImpl;
	private ReplicaListener replicaListener;
	
	private static ReplicaMember currentInstance;
	
	private ReplicaMember(int processId, String state, String boxOfficeID, String groupName) {
		this.processId = processId;
		this.state = state;
		this.boxOfficeId = boxOfficeID;
		this.groupName = groupName;
		ReplicaMember.currentInstance = this;
		grupMembershipManager = GroupMembershipManager.currentInstance();
		leaderElectionManager = LeaderElectionManager.currentInstance();
		failureDetector = new FailureDetector();
		boxOfficeImpl = new BoxOfficeImpl();
		boxOfficeImpl.setBoxOfficeID(ApplicationConfig.get(REPLICA_BOX_OFFICE_ID));
		boxOfficeImpl.setShowsBaseDirectory(ApplicationConfig.get(REPLICA_SHOWS_BASE_DIRECTORY));
		replicaListener = new ReplicaListener();
	}

	public BoxOfficeImpl getBoxOfficeImpl() {
		return boxOfficeImpl;
	}

	public void setBoxOfficeImpl(BoxOfficeImpl boxOfficeImpl) {
		this.boxOfficeImpl = boxOfficeImpl;
	}

	public String getGroupName() {
		return groupName;
	}

	public void setGroupName(String groupName) {
		this.groupName = groupName;
	}

	public LeaderElectionManager getLeaderElectionManager() {
		return leaderElectionManager;
	}

	public void setLeaderElectionManager(LeaderElectionManager leaderElectionManager) {
		this.leaderElectionManager = leaderElectionManager;
	}

	public GroupMembershipManager getGrupMembershipManager() {
		return grupMembershipManager;
	}

	public void setGrupMembershipManager(GroupMembershipManager grupMembershipManager) {
		this.grupMembershipManager = grupMembershipManager;
	}

	public FailureDetector getFailureDetector() {
		return failureDetector;
	}

	public void setFailureDetector(FailureDetector failureDetector) {
		this.failureDetector = failureDetector;
	}
	
	public static ReplicaMember currentInstance() {
		if (currentInstance != null) {
			return currentInstance;
		} else {
			System.err.println("ReplicaMember not properly initialized!");
			return null;
		}
		
	}
	
	public boolean isLeader() {
		return REPLICA_STATE_LEADER.equals(state);
	}
	
	public int getProcessId() {
		return processId;
	}

	public void setProcessId(int processId) {
		this.processId = processId;
	}

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
		if (REPLICA_STATE_LEADER.equals(this.state)) {
			System.out.println("	>>> I am the leader! :)");
			registerBoxOfficeRemoteObject();
		} else {
			unregisterBoxOfficeRemoteObject();
		}
	}
	

	public String getBoxOfficeId() {
		return boxOfficeId;
	}

	public void setBoxOfficeId(String boxOfficeId) {
		this.boxOfficeId = boxOfficeId;
	}
	
	public void startReplicaServices() {
		grupMembershipManager.startService();
		failureDetector.start();
		leaderElectionManager.startService();
		replicaListener.startListener();
	}
	
	private void registerBoxOfficeRemoteObject() {
		try {
			String[] orbArgs = ApplicationConfig.get(ApplicationConfig.CORBA_PARAMS).split(" ");
			// create and initialize the ORB
			ORB orb = ORB.init(orbArgs, null);
			// get reference to rootpoa & activate the POAManager
			POA rootpoa = (POA) orb.resolve_initial_references("RootPOA");
			rootpoa.the_POAManager().activate();
			// create servant and register it with the ORB
			boxOfficeImpl.setORB(orb);
			boxOfficeImpl.setBoxOfficeID(boxOfficeId);
			boxOfficeImpl.setShowsBaseDirectory(ApplicationConfig.get(REPLICA_SHOWS_BASE_DIRECTORY));

			// get object reference from the servant
			org.omg.CORBA.Object ref = rootpoa.servant_to_reference(boxOfficeImpl);
			// and cast the reference to a CORBA reference
			MyService href = MyServiceHelper.narrow(ref);

			// get the root naming context
			// NameService invokes the transient name service
			org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
			// Use NamingContextExt, which is part of the
			// Interoperable Naming Service (INS) specification.
			NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);

			// bind the Object Reference in Naming
			NameComponent path[] = ncRef.to_name(LEADER_REPLICA_SERVICE_NAME + "_" + this.boxOfficeId);
			ncRef.rebind(path, href);
			// wait for invocations from clients
			orb.run();
			System.out.println("BoxOffice Service ready and waiting ...");
		} catch (Exception e) {
			System.err.println("ERROR: " + e);
			e.printStackTrace(System.out);
		}
	}

	private void unregisterBoxOfficeRemoteObject() {
		try {
			String[] orbArgs = ApplicationConfig.get(ApplicationConfig.CORBA_PARAMS).split(" ");
			// create and initialize the ORB
			ORB orb = ORB.init(orbArgs, null);
			// get reference to rootpoa & activate the POAManager
			POA rootpoa = (POA) orb.resolve_initial_references("RootPOA");
			rootpoa.the_POAManager().activate();
			// create servant and register it with the ORB
			boxOfficeImpl.setORB(orb);

			// get the root naming context
			// NameService invokes the transient name service
			org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
			// Use NamingContextExt, which is part of the
			// Interoperable Naming Service (INS) specification.
			NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);

			// bind the Object Reference in Naming
			NameComponent path[] = ncRef.to_name(boxOfficeId);
			ncRef.unbind(path);
			System.out.println("BoxOffice Service was unbound successfully...");
		} catch (Exception e) {
			System.err.println("ERROR: " + e);
			e.printStackTrace(System.out);
		}
	}
	
	/**
	 * Replica entry point.
	 * @param args: args[0]: Replica configuration file.
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	public static void main(String[] args) throws FileNotFoundException, IOException {
		if (args.length != 1) {
			System.err.println("Usage error, Please provide replica configuration file.");
		}
		
		String configFilename = args[0];
		System.out.println("INFO: Loading config file: ..." + configFilename);
		Properties properties = new Properties();
		properties.load(new FileInputStream(configFilename));
		properties.list(System.out);
		ApplicationConfig.loadConfig(properties);
		System.out.println("INFO: Replica Confing loaded.");
		ReplicaMember replica = new ReplicaMember(
				ApplicationConfig.getAsInt(REPLICA_PROCESS_ID), 
				ApplicationConfig.get(REPLICA_STATE),
				ApplicationConfig.get(REPLICA_BOX_OFFICE_ID),
				ApplicationConfig.get(REPLICA_GROUP_NAME)
			);
		Runtime.getRuntime().addShutdownHook(new Thread() {
		    public void run() {
		    	GroupMembershipManager.currentInstance().disconnect();
		    }
		});
		replica.startReplicaServices();
	}


}
